﻿using System;
using System.Collections.Generic;
using System.IO;

namespace Viyi.Util.Codec
{
    public abstract partial class TextCodec : ITextCodec
    {
        static Dictionary<string, Func<ITextCodec>> factories;

        static TextCodec()
        {
            factories = new Dictionary<string, Func<ITextCodec>>(StringComparer.OrdinalIgnoreCase) {
                ["base64"] = () => new Base64Codec(),
                ["hex"] = () => new HexCodec()
            };
        }

        /// <summary>
        /// 根据给定的名称 `name` 创建 `ITextCodec` 对象。
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public static ITextCodec Create(string name)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentNullException(nameof(name));
            }

            Func<ITextCodec> factory;
            factories.TryGetValue(name, out factory);

            if (factory == null)
            {
                throw new NotSupportedException($"not suport text codec algorithm named {name}");
            }

            return factory();
        }

        public static bool IsRegistered(string name) => factories.ContainsKey(name);

        /// <summary>
        /// 注册一个 `ITextCodec` 创建器。如果指定名称的创建器已经存在，
        /// 替换它并将旧的创建器返回出来，否则返回 `null`。
        /// </summary>
        /// <param name="name"></param>
        /// <param name="factory"></param>
        /// <returns></returns>
        public static Func<ITextCodec> Register(string name, Func<ITextCodec> factory)
        {
            Func<ITextCodec> existFactory;
            factories.TryGetValue(name, out existFactory);
            factories[name] = factory;
            return existFactory;
        }

        /// <summary>
        /// 删除指定名称的创建器，并将它返回出来。如果不存在指定名称的创建器，返回 `null`。
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public static Func<ITextCodec> Deregister(string name)
        {
            Func<ITextCodec> existFactory;
            factories.TryGetValue(name, out existFactory);
            factories.Remove(name);
            return existFactory;
        }

        public int LineLength { get; set; }
        public string EndOfLine
        {
            get { return endOfLine ?? (endOfLine = Environment.NewLine); }
            set { endOfLine = value; }
        }
        string endOfLine;

        public abstract void Decode(Stream outStream, IEnumerable<string> lines);

        public virtual byte[] Decode(string code)
        {
            using (var stream = new MemoryStream())
            {
                Decode(stream, new[] { code });
                return stream.ToArray();
            }
        }

        public virtual void Decode(Stream outStream, TextReader reader)
        {
            Decode(outStream, ReadLines(reader));
        }

        protected virtual IEnumerable<string> ReadLines(TextReader reader)
        {
            using (reader)
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    yield return line;
                }
            }
        }

        protected abstract void Encode(ICharWriter writer, IEnumerable<byte[]> data);

        protected virtual ICharWriter MakeCharWriter(TextWriter writer)
            => LineLength <= 0
                ? (ICharWriter) new DirectCharWriter(writer)
                : new LineBreakCharWriter(writer, LineLength, EndOfLine);

        public virtual void Encode(TextWriter writer, IEnumerable<byte[]> data)
        {
            Encode(MakeCharWriter(writer), data);
        }

        public virtual string Encode(byte[] data) => Encode(data, 0, data.Length);

        public virtual string Encode(byte[] data, int start, int length)
        {
            byte[] segment = new byte[length];
            Array.Copy(data, start, segment, 0, length);
            return Encode(new byte[][] { segment });
        }

        public virtual string Encode(IEnumerable<byte[]> data)
        {
            using (var writer = new StringWriter())
            {
                Encode(writer, data);
                return writer.ToString();
            }
        }

        public virtual string Encode(Stream inStream)
        {
            using (var writer = new StringWriter())
            {
                Encode(writer, inStream);
                return writer.ToString();
            }
        }

        public virtual void Encode(TextWriter writer, Stream inStream)
        {
            Encode(writer, ReadBytes(inStream));
        }

        protected virtual IEnumerable<byte[]> ReadBytes(Stream inStream, int bufferSize = 64 * 1024)
        {
            byte[] buffer = new byte[bufferSize];
            using (inStream)
            {
                int count;
                while ((count = inStream.Read(buffer, 0, bufferSize)) == bufferSize)
                {
                    yield return buffer;
                }

                if (count > 0)
                {
                    byte[] more = new byte[count];
                    Array.Copy(buffer, 0, more, 0, count);
                    yield return more;
                }
            }
        }

    }
}
